package android.util.test;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.util.Log;

import com.android.gallery3d.app.AbstractGalleryActivity;
import com.android.gallery3d.app.LoadingListener;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.ui.SynchronizedHandler;

public class CustomDataLoader {

	private static final String TAG = CustomPage.class.getSimpleName();

	public static final int INDEX_NONE = -1;

	public static final int MIN_LOAD_COUNT = 2;

	public static final int MSG_LOAD_START = 3;

	public static final int MSG_LOAD_FINISH = 4;

	public static final int MSG_RUN_OBJECT = 5;

	public interface DataListener {
		public void onContentChanged(int index);

		public void onSizeChanged(int size);
	}

	private final MediaSet[] mData;
	private final MediaItem[] mCoverItem;
	private final int[] mTotalCount;
	private final long[] mItemVersion;
	private final long[] mSetVersion;

	private int mActiveStart = 0;
	private int mActiveEnd = 0;

	private int mContentStart = 0;
	private int mContentEnd = 0;

	private final MediaSet mSource;
	private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
	private int mSize;

	private DataListener mDataListener;
	private LoadingListener mLoadingListener;

	private final Handler mMainHandler;

	private ReloadTask mReloadTask;

	private final mySourceListener mSourceListener = new mySourceListener();

	public CustomDataLoader(AbstractGalleryActivity mActivity,
			MediaSet mediaSet, int cacheSize) {
		mSource = Utils.checkNotNull(mediaSet);
		mCoverItem = new MediaItem[cacheSize];
		mData = new MediaSet[cacheSize];
		mTotalCount = new int[cacheSize];
		mItemVersion = new long[cacheSize];
		mSetVersion = new long[cacheSize];
		Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
		Arrays.fill(mSetVersion, MediaObject.INVALID_DATA_VERSION);

		mMainHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case MSG_LOAD_START:
					Log.d(TAG, "Main-Handler : MSG_LOAD_START");
					if (mLoadingListener != null) {
						mLoadingListener.onLoadingStarted();
					}
					break;
				case MSG_LOAD_FINISH:
					Log.d(TAG, "Main-Handler : MSG_LOAD_FINISH");
					if (mLoadingListener != null) {
						mLoadingListener.onLoadingFinished(false);
					}
					break;
				case MSG_RUN_OBJECT:
					Log.d(TAG, "Main-Handler : MSG_RUN_OBJECT");
					((Runnable) msg.obj).run();
					break;
				default:
					break;
				}
			}
		};
	}

	public void resume() {
		Log.d(TAG, "resume");
		mSource.addContentListener(mSourceListener);
		mReloadTask = new ReloadTask();
		mReloadTask.start();
	}

	public void pause() {
		Log.d(TAG, "pause");
		mReloadTask.terminate();
		mReloadTask = null;
		mSource.removeContentListener(mSourceListener);
	}

	// private void clearSlot(int slotIndex) {
	// mData[slotIndex] = null;
	// mCoverItem[slotIndex] = null;
	// mTotalCount[slotIndex] = 0;
	// mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
	// mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
	// }

	private class mySourceListener implements ContentListener {

		@Override
		public void onContentDirty() {
			mReloadTask.notifyDirty();
		}
	}

	public void setOnDataChangedListener(DataListener dataListener) {
		this.mDataListener = dataListener;
	}

	public void setLoadingListener(LoadingListener loadingListener) {
		this.mLoadingListener = loadingListener;
	}

	private static class UpdateInfo {
		public long version;
		public int index;

		public int size;
		public MediaSet item;
		public MediaItem cover;
		public int totalCount;
	}

	private class getUpdateInfo implements Callable<UpdateInfo> {
		private final long mVersion;

		public getUpdateInfo(long version) {
			this.mVersion = version;
		}

		private int getInvalidIndex(long version) {
			long[] setVersion = mSetVersion;
			int length = setVersion.length;
			for (int i = mContentStart, n = mActiveEnd; i < n; ++i) {
				int index = i % length;
				if (setVersion[index] != index) {
					return i;
				}
			}
			return INDEX_NONE;
		}

		@Override
		public UpdateInfo call() throws Exception {
			int index = getInvalidIndex(mVersion);
			if (index == INDEX_NONE && mSourceVersion == mVersion) {
				return null;
			}
			UpdateInfo update = new UpdateInfo();
			update.version = mSourceVersion;
			update.index = index;
			update.size = mSize;
			return update;
		}
	}

	private class UpdateContent implements Callable<Void> {
		private final UpdateInfo mUpdateInfo;

		public UpdateContent(UpdateInfo updateInfo) {
			this.mUpdateInfo = updateInfo;
		}

		@Override
		public Void call() throws Exception {
			if (mReloadTask == null) {
				return null;
			}
			UpdateInfo update = mUpdateInfo;
			mSourceVersion = update.version;
			if (mSize != update.size) {
				mSize = update.size;
				if (mDataListener != null) {
					mDataListener.onSizeChanged(mSize);
				}
				if (mContentEnd > mSize) {
					mContentEnd = mSize;
				}
				if (mActiveEnd > mSize) {
					mActiveEnd = mSize;
				}
			}
			if (update.index >= mContentStart && update.index < mContentEnd) {
				int pos = update.index % mCoverItem.length;
				mSetVersion[pos] = update.version;
				long itemVersion = update.item.getDataVersion();
				if (mItemVersion[pos] == itemVersion) {
					return null;
				}
				mItemVersion[pos] = update.version;
				mData[pos] = update.item;
				mCoverItem[pos] = update.cover;
				mTotalCount[pos] = update.totalCount;
				if (mDataListener != null && update.index >= mActiveStart
						&& update.index < mActiveEnd) {
					mDataListener.onSizeChanged(update.index);
				}
			}
			return null;
		}

	}

	private <T> T executeAndWait(Callable<T> callable) {
		FutureTask<T> task = new FutureTask<T>(callable);
		mMainHandler.obtainMessage(MSG_RUN_OBJECT, task).sendToTarget();
		try {
			return task.get();
		} catch (InterruptedException e) {
			return null;
		} catch (ExecutionException e) {
			throw new RuntimeException();
		}
	}

	private class ReloadTask extends Thread {
		private volatile boolean mActive = true;
		private volatile boolean mDrity = true;
		private volatile boolean mIsLoading = false;

		private void updateLoading(boolean isLoading) {
			if (mIsLoading == isLoading) {
				return;
			}
			mIsLoading = isLoading;
			mMainHandler.obtainMessage(
					isLoading ? MSG_LOAD_START : MSG_LOAD_FINISH)
					.sendToTarget();
		}

		@Override
		public void run() {
			Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
			boolean updateComple = false;

			while (mActive) {
				synchronized (this) {
					if (mActive && !mDrity && updateComple) {
						if (!mSource.isLoading()) {
							updateLoading(false);
						}
						Utils.waitWithoutInterrupt(this);
						continue;
					}
				}

				mDrity = false;
				updateLoading(true);

				long version = mSource.reload();
				UpdateInfo update = executeAndWait(new getUpdateInfo(version));
				updateComple = update == null;
				if (updateComple) {
					continue;
				}
				if (update.version != version) {
					update.version = version;
					update.size = mSource.getSubMediaSetCount();

					if (update.index >= update.size) {
						update.index = INDEX_NONE;
					}
				}
				if (update.index != INDEX_NONE) {
					update.item = mSource.getSubMediaSet(update.index);
					if (update.item == null) {
						continue;
					}
					update.cover = update.item.getCoverMediaItem();
					update.totalCount = update.item.getTotalMediaItemCount();
				}
				executeAndWait(new UpdateContent(update));
			}
			updateLoading(false);
		}

		public synchronized void notifyDirty() {
			mDrity = true;
			notifyAll();
		}

		public synchronized void terminate() {
			mDrity = false;
			notifyAll();
		}
	}

}
